package com.cd.socialMarketing.repository;

/** @author Agustin Cingolani
 *  02/05/2013
 */

import java.io.Serializable;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.cd.socialMarketing.model.AbstractEntityLong;

@SuppressWarnings({"unchecked","hiding","rawtypes"})
public class HibernateObjectDao<K extends Serializable, E extends AbstractEntityLong> extends HibernateDaoSupport{
	
	private Class<E> type;
	
	public HibernateObjectDao(){		
	};
	
	public HibernateObjectDao(Class<E> type){
		this.type = type;
	};
	
    public Class<E> getPersistentClass(){
    	return type;
    };
    
    public E get(K id){
    	return (E) getHibernateTemplate().get(getPersistentClass(), id);
    }
    
	public List<E> list(){
    	return createCriteria().list();
    }
	
	public List<E> list(String property, String type) {
		Criteria criteria = createCriteria();
		
		if (type!=null && type.equalsIgnoreCase("asc"))
			return list(criteria.addOrder(Order.asc(property)));
		else if (type!=null && type.equalsIgnoreCase("desc"))
			return list(criteria.addOrder(Order.desc(property)));
		else
			return list(criteria);
	}
	
	public List<E> list(Integer from, Integer count){
		Criteria criteria = createCriteria();
    	return list(criteria, from, count);
    }
	
	public List<E> list(Criteria criteria, Integer from, Integer count){
		criteria.setFirstResult(from);
		criteria.setMaxResults(count);
    	return criteria.list();
    }
	
	public List<E> list(Integer from, Integer count, String property, String type) {
		Criteria criteria = createCriteria();
		criteria.setFirstResult(from);
		criteria.setMaxResults(count);
		
		if (type!=null && type.equalsIgnoreCase("asc"))
			return list(criteria.addOrder(Order.asc(property)));
		else if (type!=null && type.equalsIgnoreCase("desc"))
			return list(criteria.addOrder(Order.desc(property)));
		else
			return list(criteria);
	}
	
	protected <E> List<E> list(Criteria criteria) {
		return criteria.list();
	}
    
    protected Criteria createCriteria() {
		return getSession().createCriteria(getPersistentClass());
	}
    
    public void save(E entity) {
    	getHibernateTemplate().saveOrUpdate(entity);
    }
    
    public void delete(E entity) {
    	getHibernateTemplate().delete(entity);
    }
    
    public <E> List<E> findByName(String nombre){
    	return (List<E>) findByName(getPersistentClass(), nombre, false);
    }
    
	protected <E> List<E> findByName(final Class<E> clazz, final String nombre, final boolean caseSensitive) {

		return (List<E>) getHibernateTemplate().execute(
				new HibernateCallback() {
			
			@Override
			public Object doInHibernate(Session session) 
				throws HibernateException, SQLException {
	
				Criteria criteria = findByNameCriteria(session, clazz, nombre, caseSensitive);
				return list(criteria);
			}
		});
	}
	
	protected <E> Criteria findByNameCriteria(Session session, Class<E> clazz,
			String nombre, boolean caseSensitive) {
		if (caseSensitive)
			return findByLikeCriteria(session, clazz, "nombre", nombre);
		else
			return findByILikeCriteria(session, clazz, "nombre", nombre);
	}

	protected <E> Criteria findByLikeCriteria(Session session, Class<E> clazz, String property, String value) {
		Criteria criteria = createCriteria(session, clazz);
		criteria.add(Restrictions.like(property, "%" + value + "%")).addOrder(Order.asc(property));
		return criteria;
	}

	protected <E> Criteria findByILikeCriteria(Session session, Class<E> clazz, String property, String value) {
		Criteria criteria = createCriteria(session, clazz);
		criteria.add(Restrictions.ilike(property, "%" + value + "%")).addOrder(Order.asc(property));
		return criteria;
	}
	
	public List<E> find(final Map<String, Object> map) {
		return (List<E>) getHibernateTemplate().execute(
				new HibernateCallback() {
			
			@Override
			public Object doInHibernate(Session session) 
				throws HibernateException, SQLException {

				return list(findCriteria(session, getPersistentClass(), map, new HashSet<String>()));
			}
		});
	}
	
	public List<E> find(final Map<String, Object> map, final Integer from, final Integer count) {
		return (List<E>) getHibernateTemplate().execute(
				new HibernateCallback() {
			
			@Override
			public Object doInHibernate(Session session) 
				throws HibernateException, SQLException {

				return list(findCriteria(session, getPersistentClass(), map, from, count, new HashSet<String>()));
			}
		});
	}
	
	public List<E> find(final Map<String, Object> map, final Integer from, final Integer count, final String property, final String type) {
		return (List<E>) getHibernateTemplate().execute(
				new HibernateCallback() {
			
			@Override
			public Object doInHibernate(Session session) 
				throws HibernateException, SQLException {

				// CREAMOS LA PROPERTY PARA ORDENAR
				PropertyAliasHelper helper = new PropertyAliasHelper(property);
				String path = helper.getPath();
				String alias = helper.getAlias();
				
				// LA AGREGAMOS A LOS PATHS UTILIZADOS PARA ALIAS, ASI NO SE DUPLICA SI HAY FILTROS
				Set<String> paths = new HashSet<String>();
				if (!alias.equalsIgnoreCase(property)){
					paths.add(path);
				}
				
				// CREMAMOS EL CRITERIA
				Criteria criteria = findCriteria(session, getPersistentClass(), map, from, count, paths);
				
				// AGREGAMOS EL ALIAS
				if (!alias.equalsIgnoreCase(property))
					criteria.createAlias(path, alias);				
				
				// ORDENAMOS SEGUN EL TIPO
				if (type!=null && type.equalsIgnoreCase("asc"))
					return list(criteria.addOrder(Order.asc(property)));
				else if (type!=null && type.equalsIgnoreCase("desc"))
					return list(criteria.addOrder(Order.desc(property)));
				else
					return list(criteria);
			}
		});
	}
	
	public List<E> find(final Criterion expression, final Integer from, final Integer count) {
		return (List<E>) getHibernateTemplate().execute(
				new HibernateCallback() {
			
			@Override
			public Object doInHibernate(Session session) 
				throws HibernateException, SQLException {

				return list(createCriteria(session, getPersistentClass()).add(expression), from, count);
			}
		});
	}
	
	public List<E> find(final Criterion expression, final Integer from, final Integer count, final String property, final String type) {
		return (List<E>) getHibernateTemplate().execute(
				new HibernateCallback() {
			
			@Override
			public Object doInHibernate(Session session) 
				throws HibernateException, SQLException {

				
				// CREAMOS LA PROPERTY PARA ORDENAR
				PropertyAliasHelper helper = new PropertyAliasHelper(property);
				String path = helper.getPath();
				String alias = helper.getAlias();
				
				// LA AGREGAMOS A LOS PATHS UTILIZADOS PARA ALIAS, ASI NO SE DUPLICA SI HAY FILTROS
				Set<String> paths = new HashSet<String>();
				if (!alias.equalsIgnoreCase(property)){
					paths.add(path);
				}
				
				// CREMAMOS EL CRITERIA
				Criteria criteria = createCriteria(session, getPersistentClass()).add(expression);
				
				// AGREGAMOS EL ALIAS
				if (!alias.equalsIgnoreCase(property))
					criteria.createAlias(path, alias);				
				
				// ORDENAMOS SEGUN EL TIPO
				if (type!=null && type.equalsIgnoreCase("asc"))
					return list(criteria.addOrder(Order.asc(property)), from, count);
				else if (type!=null && type.equalsIgnoreCase("desc"))
					return list(criteria.addOrder(Order.desc(property)), from, count);
				else
					return list(criteria, from, count);
				
			}
		});
	}

	
	public Long count(final Map<String, Object> map) {
		return (Long) getHibernateTemplate().execute(
				new HibernateCallback() {
			
			@Override
			public Long doInHibernate(Session session) 
				throws HibernateException, SQLException {

				// CREMAMOS EL CRITERIA
				Criteria criteria = findCriteria(session, getPersistentClass(), map, new HashSet<String>());
				criteria.setProjection(Projections.rowCount());
				
				List results = criteria.list();
				if (results!=null) {
					return (Long.valueOf(Integer.toString((Integer)results.get(0))));
				}
				return 0L;
			}
		});
	}
	
	public Long count(final Criterion expression) {
		return (Long) getHibernateTemplate().execute(
				new HibernateCallback() {
			
			@Override
			public Long doInHibernate(Session session) 
				throws HibernateException, SQLException {

				// CREMAMOS EL CRITERIA
				Criteria criteria = createCriteria(session, getPersistentClass()).add(expression);
				criteria.setProjection(Projections.rowCount());
				
				List results = criteria.list();
				if (results!=null) {
					return (Long.valueOf(Integer.toString((Integer)results.get(0))));
				}
				return 0L;
			}
		});
	}
	
	public E findUnique(Map<String, Object> map) {
		return findUnique(getPersistentClass(), map);
	}
	
	protected <E> E findUnique(final Class<E> clazz, final Map<String, Object> map) {

		return (E) getHibernateTemplate().execute(
				new HibernateCallback() {
			
			@Override
			public Object doInHibernate(Session session) 
				throws HibernateException, SQLException {

				return (E) findCriteria(session, clazz, map, new HashSet<String>()).uniqueResult();
			}
		});
	}
	
	protected <E> Criteria findCriteria(Session session, Class<E> clazz, Map<String, Object> map, Set<String> paths) {
		Criteria criteria = createCriteria(session, clazz);
		addMapFilters(criteria, map, paths);
		return criteria;
	}
	
	protected <E> Criteria findCriteria(Session session, Class<E> clazz, Map<String, Object> map, Integer from, Integer count, Set<String> paths) {
		Criteria criteria = findCriteria(session, clazz, map, paths);
		criteria.setFirstResult(from);
		criteria.setMaxResults(count);
		return criteria;
	}
	
	protected Criteria createCriteria(Session session, Class<?> clazz) {
		return session.createCriteria(clazz);
	}
	
	protected Criteria addMapFilters(Criteria criteria, Map<String, Object> map,
			Set<String> paths) {
		if (map != null) {
			for (Map.Entry<String, Object> m : map.entrySet()){
				String propertyExpression = m.getKey();
				Object value = m.getValue();
				
				addPropertyExpressionRestriction(criteria, propertyExpression, value, paths);
			}
		}
		return criteria;
	}
	
	private Criteria addPropertyExpressionRestriction(Criteria criteria,
			String propertyExpression, Object value, Set<String> paths) {
		PropertyAliasHelper helper = new PropertyAliasHelper(propertyExpression);
		String path = helper.getPath();
		String alias = helper.getAlias();
		String prop = helper.getProperty();
		if(!path.equals(prop) && !paths.contains(path)){
			paths.add(path);
			criteria.createAlias(path, alias);
		}
		if (value == null)
			return criteria.add(Restrictions.isNull(helper.getNewExpression()));
		else if (value instanceof String && ((String)value).contains("%")){
			return criteria.add(Restrictions.ilike(helper.getNewExpression(), value));
		}else
			return criteria.add(Restrictions.eq(helper.getNewExpression(), value));
	}

}